Intro To Assembly
So first off, what is assembly code? Assembly code is the code that actually runs on your computer by the processor. For instance take some C code:
#include <stdio.h> void main(void) { puts("Hello World!"); }
That code isn't ran. Thing is that code is compiled into assembly code, which looks like this:
0000000000001135 <main>: 1135: 55 push rbp 1136: 48 89 e5 mov rbp,rsp 1139: 48 8d 3d c4 0e 00 00 lea rdi,[rip+0xec4] # 2004 <_IO_stdin_used+0x4> 1140: e8 eb fe ff ff call 1030 <puts@plt> 1145: 90 nop 1146: 5d pop rbp 1147: c3 ret 1148: 0f 1f 84 00 00 00 00 nop DWORD PTR [rax+rax*1+0x0] 114f: 00
The purpose of languages like C, is that we can program without having to really deal with assembly code. We write code that is handed to a compiler, and the compiler takes that code and generates assembly code that will accomplish whatever the C code tells it to. Then the assembly code is what is actually ran on the processor. Since this is the code that is actually ran, it helps to understand it. Also since most of the time we are handed compiled binaries we only have the assembly code to work from. However we have tools such as Ghidra that will take compiled assembly code and give us a view of what it thinks the C code that the code was compiled from looks like, so we don't need to read endless lines of assembly code.
Also with assembly code, there is a lot of different architectures. Different types of processors can run different types of assembly code architectures. The two we are dealing with the most here will be 64 bit, and 32 bit ELF (Executable and Linkable Format). I will often call these two things x64
and x86
.
Now one of the most common memory regions you will be dealing with is the stack. It is where local variables in the code are stored.
For instance, in this code the variable x
is stored in the stack:
#include <stdio.h> void main(void) { int x = 5; puts("hi"); }
Specifically we can see it is stored on the stack at rbp-0x4
.
``0000000000001135 <main>: 1135: 55 push rbp 1136: 48 89 e5 mov rbp,rsp 1139: 48 83 ec 10 sub rsp,0x10 113d: c7 45 fc 05 00 00 00 mov DWORD PTR [rbp-0x4],0x5 1144: 48 8d 3d b9 0e 00 00 lea rdi,[rip+0xeb9] # 2004 <_IO_stdin_used+0x4> 114b: e8 e0 fe ff ff call 1030 <puts@plt> 1150: 90 nop 1151: c9 leave 1152: c3 ret 1153: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0] 115a: 00 00 00 115d: 0f 1f 00 nop DWORD PTR [rax]
Now values on the stack are moved on by either pushing them onto the stack, or popping them off. That is the only way to add or remove values from the stack (it is a LIFO data structure). However we can reference values on the stack.
The exact bounds of the stack is recorded by two registers, rbp
and rsp
. The base pointer rbp
points to the bottom of the stack. The stack pointer rsp
points to the top of the stack.